package com.guavamail.smtp.auth.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import com.guavamail.smtp.SMTPContext;
import com.guavamail.smtp.exception.LoginFailedException;
import com.guavamail.smtp.auth.AuthenticationHandler;
import com.guavamail.smtp.auth.Credential;
import com.guavamail.smtp.auth.LoginValidator;
import com.guavamail.util.Base64;

/**
 * Implements the SMTP AUTH LOGIN mechanism.<br />
 * You are only required to plug your LoginValidator implementation for username
 * and password validation to take effect.
 * 
 * @author Marco Trevisan <mrctrevisan@yahoo.it>
 * @author De Oliveira Edouard &lt;doe_wanted@yahoo.fr&gt;
 */
public class LoginAuthenticationHandler implements AuthenticationHandler {
	private String username;

	private String password;

	private LoginValidator helper;

	private static List<String> AUTH_MECH;

	/** Creates a new instance of LoginAuthenticationHandler */
	public LoginAuthenticationHandler(LoginValidator helper) {
		this.helper = helper;

		AUTH_MECH = new ArrayList<String>(1);
		AUTH_MECH.add("LOGIN");
	}

	/**
	 * 
	 */
	public List<String> getAuthenticationMechanisms() {
		return AUTH_MECH;
	}

	/**
	 * 
	 */
	public boolean auth(String clientInput, StringBuilder response, SMTPContext ctx) throws LoginFailedException {
		StringTokenizer stk = new StringTokenizer(clientInput);
		String token = stk.nextToken();
		if (token.trim().equalsIgnoreCase("AUTH")) {
			// The RFC2554 "initial-response" parameter must not be present
			// The line must be in the form of "AUTH LOGIN"
			if (!stk.nextToken().trim().equalsIgnoreCase("LOGIN")) {
				// Mechanism mismatch
				response.append("504 AUTH mechanism mismatch");
				return true;
			}

			if (stk.hasMoreTokens()) {
				// the client submitted an initial response
				response.append("535 Initial response not allowed in AUTH LOGIN");
				return true;
			}

			response.append("334 ").append(Base64.encodeToString("Username:".getBytes(), false));
			return false;
		}

		if (username == null) {
			byte[] decoded = Base64.decode(clientInput);
			if (decoded == null) {
				throw new LoginFailedException();
			}
			this.username = new String(decoded);
			response.append("334 ").append(Base64.encodeToString("Password:".getBytes(), false));
			return false;
		}

		byte[] decoded = Base64.decode(clientInput);
		if (decoded == null) {
			throw new LoginFailedException();
		}

		this.password = new String(decoded);

		try {
			helper.login(username, password);
			resetState();
		} catch (LoginFailedException lfe) {
			resetState();
			throw lfe;
		}

		ctx.setCredential(new Credential(username));
		return true;
	}

	/**
	 * 
	 */
	public void resetState() {
		this.username = null;
		this.password = null;
	}
}
